<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>14.3.2-实现 Transition 组件</title>
</head>

<body>
    <div id="app">
        <!-- <input value="foo" id="my-input" type="text" value="foo" class="foo" onchange="change()" /> -->
    </div>
</body>
    <script src="https://unpkg.com/@vue/reactivity@3.0.5/dist/reactivity.global.js"></script>
    <script>
        /**
         * Transition 组件是基于虚拟 DOM 实现的
         * - 整个过渡过程可以抽象为几个阶段，这些阶段可以抽象为特定的回调函数。
         *    例如beforeEnter、enter、leave 等
         * - 基于虚拟 DOM 的实现也需要将 DOM 元素的生命周期分割为这样几个阶段，
         *    并在特定阶段执行对应的回调函数
         * 
         * 几点重要信息
         * - Transition 组件本身不会渲染任何额外的内容，
         *    它只是通过默认插槽读取过渡元素，并渲染需要过渡的元素
         * - Transition 组件的作用，就是在过渡元素的虚拟节点上添加 transition 相关的钩子函数
         * 
         * */ 
        
        const { effect, ref, reactive, shallowReactive, shallowReadonly } = VueReactivity;
        // 文本节点的 type 标识
        const Text = Symbol();
        const Comment = Symbol();
        const Fragment = Symbol();
        // 全局变量，存储当前正在被初始化的组件实例
        let currentInstance = null;
        // 该方法接收组件实例作为参数，并将该实例设置为 currentInstance
        function setCurrentInstance(instance) {
            currentInstance = instance
        }
        /**
         * onMounted
         * 将生命周期钩子函数添加到当前实例对象的 instance.mounted 数组中
         * @param {Function} fn 生命周期钩子函数
         * @returns void
         **/
         function onMounted(fn) {
            if (currentInstance) {
                // 将生命周期函数添加到 instance.mounted 数组中
                currentInstance.mounted.push(fn)
            } else {
                console.error('onMounted 函数只能在 setup 中调用')
            }
        }
        /**
         * 将生命周期钩子函数添加到当前实例对象的 instance.unmounted 数组中
         * @param {Function} fn 生命周期钩子函数
         * @returns void
         **/
         function onUnmounted(fn) {
            if (currentInstance) {
                // 将生命周期函数添加到 instance.mounted 数组中
                currentInstance.unmounted.push(fn)
            } else {
                console.error('unmounted 函数只能在 setup 中调用')
            }
        }

        
         function createRenderer(options) {
            // 通过 options 得到操作 DOM 的 API
            const {
                createElement,
                insert,
                setElementText,
                patchProps,
                createText,
                setText,
                createComment, 
            } = options

            // 在这个作用域内定义的函数都可以访问那些 API
            /**
             * 在这个作用域内定义的函数都可以访问那些 API
             * @param {VNode} vnode 虚拟 DOM 对象
             * @param {HTMLElement} container 容器
             * @param {HTMLElement} anchor 锚点元素
             * */ 
            function mountElement(vnode, container, anchor) {
                // 调用 createElement 函数创建元素
                // 让 vnode.el 引用真实 DOM 元素
                const el = vnode.el = createElement(vnode.type)
                if (typeof vnode.children === 'string') {
                    // 调用 setElementText 设置元素的文本节点
                    setElementText(el, vnode.children)
                }else if (Array.isArray(vnode.children)) {
                    // 如果 children 是数组，则遍历每一个子节点，并调用 patch 函数挂载它们
                    vnode.children.forEach(child => {
                        patch(null, child, el)
                    })
                }

                // 如果 vnode.props 存在才处理它
                if (vnode.props) {
                    // 遍历 vnode.props
                    for (const key in vnode.props) {
                        // 调用 patchProps 函数即可
                        patchProps(el, key, null, vnode.props[key])
                    }
                }

                // 判断一个 VNode 是否需要过渡
                const needTransition = vnode.transition // 新增
                if (needTransition) { // 新增
                    // 调用 transition.beforeEnter 钩子，并将 DOM 元素作为参数传递
                    vnode.transition.beforeEnter(el)
                } 

                // 调用 insert 函数将元素插入到容器内
                insert(el, container, anchor)

                if (needTransition) { // 新增
                    // 调用 transition.enter 钩子，并将 DOM 元素作为参数传递
                    vnode.transition.enter(el)
                }
            }

            function patchElement(n1, n2) {
                const el = n2.el = n1.el
                const oldProps = n1.props
                const newProps = n2.props
                // 第一步：更新 props
                for (const key in newProps) {
                    if (newProps[key] !== oldProps[key]) {
                        patchProps(el, key, oldProps[key], newProps[key])
                    }
                }
                for (const key in oldProps) {
                    if (!(key in newProps)) {
                        patchProps(el, key, oldProps[key], null)
                    }
                }
                // 第二步：更新 children
                patchChildren(n1, n2, el)
            }
            /**
             * @param {VNode} n1 旧的 vnode
             * @param {VNode} n2 新的 vnode
             * @param {HTMLElement} container 容器
             * */ 
            function patchChildren(n1, n2, container) {
                // 判断新子节点的类型是否是文本节点
                if (typeof n2.children === 'string') {
                    // 旧子节点的类型有三种可能：没有子节点、文本子节点以及一组子节点
                    // 只有当旧子节点为一组子节点时，才需要逐个卸载，其他情况下什么都不需要做
                    if (Array.isArray(n1.children)) {
                        n1.children.forEach((c) => unmount(c))
                    }
                    // 最后将新的文本节点内容设置给容器元素
                    setElementText(container, n2.children)
                }else if (Array.isArray(n2.children)) {
                    // 说明新子节点是一组子节点
                    // 判断旧子节点是否也是一组子节点
                    if (Array.isArray(n1.children)) {
                        // 代码运行到这里，说明新旧子节点都是一组子节点
                        // 封装 patchKeyedChildren 函数处理两组子节点
                        patchKeyedChildren(n1, n2, container)
                    } else {
                        // 此时：
                        // 旧子节点要么是文本子节点，要么不存在
                        // 但无论哪种情况，我们都只需要将容器清空，然后将新的一组子节点逐个挂载
                        setElementText(container, '')
                        n2.children.forEach(c => patch(null, c, container))
                    }
                }else{
                    // 代码运行到这里，说明新子节点不存在
                    // 旧子节点是一组子节点，只需逐个卸载即可
                    if (Array.isArray(n1.children)) {
                        console.log('null unmount')
                        n1.children.forEach(c => unmount(c))
                    } else if (typeof n1.children === 'string') {
                        // 旧子节点是文本子节点，清空内容即可
                        setElementText(container, '')
                    }
                    // 如果也没有旧子节点，那么什么都不需要做
                }


                function patchKeyedChildren(n1, n2, container){
                    console.log('patchKeyedChildren run')
                    const oldChildren = n1.children
                    const newChildren = n2.children

                    // 处理相同的前置节点
                    // 索引 j 指向新旧两组子节点的开头
                    let j = 0
                    let oldVNode = oldChildren[j]
                    let newVNode = newChildren[j]
                    // while 循环向后遍历，直到遇到拥有不同 key 值的节点为止
                    while (oldVNode?.key === newVNode?.key) {
                        // 调用 patch 函数进行更新
                        patch(oldVNode, newVNode, container)
                        // 更新索引 j，让其递增
                        j++
                        oldVNode = oldChildren[j]
                        newVNode = newChildren[j]
                        // 容错处理，防止索引越界
                        if(!oldVNode&&!newVNode)break;
                    }
                    
                    // 容错处理，防止两组相同的节点数量
                    if(j>=oldChildren.length && j>=newChildren.length)return;

                    // 更新相同的后置节点
                    // 索引 oldEnd 指向旧的一组子节点的最后一个节点
                    let oldEnd = oldChildren.length - 1
                    // 索引 newEnd 指向新的一组子节点的最后一个节点
                    let newEnd = newChildren.length - 1

                    oldVNode = oldChildren[oldEnd]
                    newVNode = newChildren[newEnd]

                    // while 循环从后向前遍历，直到遇到拥有不同 key 值的节点为止
                    while (oldVNode.key === newVNode.key) {
                        // 调用 patch 函数进行更新
                        patch(oldVNode, newVNode, container)
                        // 递减 oldEnd 和 newEnd
                        oldEnd--
                        newEnd--
                        oldVNode = oldChildren[oldEnd]
                        newVNode = newChildren[newEnd]
                    }

                    // 预处理完毕后，如果满足如下条件，则说明从 j --> newEnd 之间的节点应作为新节点插入
                    if (j > oldEnd && j <= newEnd) {
                        // 锚点的索引
                        const anchorIndex = newEnd + 1
                        // 锚点元素
                        const anchor = anchorIndex < newChildren.length ? newChildren[anchorIndex].el : null
                        // 采用 while 循环，调用 patch 函数逐个挂载新增节点
                        while (j <= newEnd) {
                            patch(null, newChildren[j++], container, anchor)
                        }
                    }else if (j > newEnd && j <= oldEnd) {
                        // j -> oldEnd 之间的节点应该被卸载
                        while (j <= oldEnd) {
                            unmount(oldChildren[j++])
                        }
                    } else {
                        // 增加 else 分支来处理非理想情况
                        // 构造 source 数组
                        // 新的一组子节点中剩余未处理节点的数量
                        const count = newEnd - j + 1
                        // 新的一组子节点中的节点在旧的一组子节点中的位置索引
                        const source = new Array(count)
                        source.fill(-1)

                        // oldStart 和 newStart 分别为起始索引，即 j
                        const oldStart = j
                        const newStart = j

                        let moved = false // 代表是否需要移动节点
                        let pos = 0 // 代表遍历旧的一组子节点的过程中遇到的最大索引值 k

                        // 构建索引表
                        const keyIndex = {} // 用来存储新节点列表的 key 和节点位置索引(index)之间的映射
                        for(let i = newStart; i <= newEnd; i++) {
                            keyIndex[newChildren[i].key] = i
                        }

                        let patched = 0 // 代表更新过的旧节点数量
                        // 遍历旧的一组子节点中剩余未处理的节点
                        for(let i = oldStart; i <= oldEnd; i++) {
                            oldVNode = oldChildren[i]
                            // 如果更新过的节点数量小于等于需要更新的节点数量，则执行更新
                            if (patched <= count) {
                                // 通过索引表快速找到新的一组子节点中具有相同 key 值的节点位置
                                const k = keyIndex[oldVNode.key]
                                if (typeof k !== 'undefined') {
                                    newVNode = newChildren[k]
                                    // 调用 patch 函数完成更新
                                    patch(oldVNode, newVNode, container)
                                    // 每更新一个节点，都将 patched 变量 +1
                                    patched++
                                    // 填充 source 数组
                                    source[k - newStart] = i
                                    // 判断节点是否需要移动
                                    if (k < pos) {
                                        moved = true
                                        console.log('moved', oldVNode);
                                    } else {
                                        pos = k
                                    }
                                } else {
                                    // 没找到
                                    unmount(oldVNode)
                                }
                            }else{
                                // 如果更新过的节点数量大于需要更新的节点数量，则卸载多余的节点
                                unmount(oldVNode)
                            }
                        }

                        if(moved){
                            // 如果 moved 为真，则需要进行 DOM 移动操作

                            // 计算最长递增子序列
                            // 返回结果是最长递增子序列中的元素在 source 数组中的位置索引
                            const seq = getSequence(source)

                            console.log('seq', seq);

                            // s 指向最长递增子序列的最后一个元素
                            let s = seq.length - 1
                            // i 指向新的一组子节点的最后一个元素
                            let i = count - 1

                            // for 循环使得 i 递减，即按照图 11-24 中箭头的方向移动
                            // 因为这是从后往前匹配，anchor直接使用下一个节点的元素就好
                            for (i; i >= 0; i--) {
                                if (source[i] === -1) {
                                    // 说明索引为 i 的节点是全新的节点，应该将其挂载
                                    // 该节点在新 children 中的真实位置索引
                                    const pos = i + newStart
                                    const newVNode = newChildren[pos]
                                    // 该节点的下一个节点的位置索引
                                    const nextPos = pos + 1
                                    // 锚点
                                    const anchor = nextPos < newChildren.length
                                        ? newChildren[nextPos].el
                                        : null
                                    // 挂载
                                    patch(null, newVNode, container, anchor)
                                } else if (i !== seq[s]) {
                                    // 如果节点的索引 i 不等于 seq[s] 的值，说明该节点需要移动
                                    // 该节点在新的一组子节点中的真实位置索引
                                    const pos = i + newStart
                                    const newVNode = newChildren[pos]
                                    // 该节点的下一个节点的位置索引
                                    const nextPos = pos + 1
                                    // 锚点
                                    const anchor = nextPos < newChildren.length
                                        ? newChildren[nextPos].el
                                        : null
                                    // 移动
                                    insert(newVNode.el, container, anchor)
                                } else {
                                    // 当 i === seq[s] 时，说明该位置的节点不需要移动
                                    // 只需要让 s 指向下一个位置
                                    s--
                                }
                            }


                            // 给定序列的递增子序列的求法不在本书的讲解范围内
                            // 如下是用于求解给定序列的最长递增子序列的代码，取自 Vue.js 3
                            function getSequence(arr){
                                const p = arr.slice()
                                const result = [0]
                                let i, j, u, v, c
                                const len = arr.length
                                for (i = 0; i < len; i++) {
                                    const arrI = arr[i]
                                    if (arrI !== 0) {
                                        j = result[result.length - 1]
                                        if (arr[j] < arrI) {
                                            p[i] = j
                                            result.push(i)
                                            continue
                                        }
                                        u = 0
                                        v = result.length - 1
                                        while (u < v) {
                                            c = ((u + v) / 2) | 0
                                            if (arr[result[c]] < arrI) {
                                                u = c + 1
                                            } else {
                                                v = c
                                            }
                                        }
                                        if (arrI < arr[result[u]]) {
                                            if (u > 0) {
                                                p[i] = result[u - 1]
                                            }
                                            result[u] = i
                                        }
                                    }
                                }
                                u = result.length
                                v = result[u - 1]
                                while (u-- > 0) {
                                    result[u] = v
                                    v = p[v]
                                }
                                return result
                            }
                        }
                    }
                }
            }

            // 完成组件渲染
            /**
             * @param {VNode} vnode 虚拟 DOM 对象
             * @param {HTMLElement} container 容器
             * @param {HTMLElement} anchor 锚点元素
             * */ 
            function mountComponent(vnode, container, anchor) {
                // 检查是否是函数式组件
                const isFunctional = typeof vnode.type === 'function'

                // 通过 vnode 获取组件的选项对象，即 vnode.type
                let componentOptions = vnode?.type || {}

                if (isFunctional){
                    // 如果是函数式组件，则将 vnode.type 作为渲染函数，将 vnode.type.props 作为 props 选项定义即可
                    componentOptions = {
                        render: vnode.type,
                        props: vnode.props
                    }
                }

                // 获取组件的渲染函数 render, 以及 data 函数
                // 从组件选项对象中取得组件的生命周期函数
                let { 
                    render, data, 
                    props: propsOption,
                    beforeCreate, created, 
                    beforeMount, mounted, beforeUpdate, updated,
                    setup
                } = componentOptions

                // 在这里调用 beforeCreate 钩子
                beforeCreate && beforeCreate()
                // 调用 data 函数得到原始数据，并调用 reactive 函数将其包装为响应式数据
                const state = data ? reactive(data()) : {};
                // 调用 resolveProps 函数解析出最终的 props 数据与 attrs 数据
                const [props, attrs] = resolveProps(propsOption, vnode.props)
                // 直接使用编译好的 vnode.children 对象作为 slots 对象即可
                const slots = vnode.children || {}
                // 定义组件实例，一个组件实例本质上就是一个对象，它包含与组件有关的状态信息
                const instance = {
                    // 组件自身的状态数据，即 data
                    state,
                    // 一个布尔值，用来表示组件是否已经被挂载，初始值为 false
                    // 将解析出的 props 数据包装为 shallowReactive 并定义到组件实例上
                    props: shallowReactive(props),
                    isMounted: false,
                    // 组件所渲染的内容，即子树（subTree）
                    subTree: null,
                    slots,
                    // 在组件实例中添加 mounted 数组，用来存储通过 onMounted 函数注册的生命周期钩子函数
                    mounted: [],
                    // 在组件实例中添加 unmounted 数组，用来存储通过 onUnmounted 函数注册的生命周期钩子函数
                    unmounted: [],
                    // 只有 KeepAlive 组件的实例下会有 keepAliveCtx 属性
                    keepAliveCtx: null
                }

                // 检查当前要挂载的组件是否是 KeepAlive 组件
                const isKeepAlive = vnode.type.__isKeepAlive
                if (isKeepAlive) {
                    // 在 KeepAlive 组件实例上添加 keepAliveCtx 对象
                    instance.keepAliveCtx = {
                        // move 函数用来移动一段 vnode
                        move: (vnode, container, anchor) => {
                            // 本质上是将组件渲染的内容移动到指定容器中，即隐藏容器中
                            insert(vnode.component.subTree.el, container, anchor)
                        },
                        createElement,
                    }
                }

                /**
                 * 定义 emit 函数，用于发射自定义事件
                 * @param {string} event 事件名称
                 * @param  {...any} payload 传递给事件处理函数的参数
                 * */ 
                 function emit(event, ...payload) {
                    // 根据约定对事件名称进行处理，例如 change --> onChange
                    const eventName = `on${event[0].toUpperCase() + event.slice(1)}`
                    // 根据处理后的事件名称去 props 中寻找对应的事件处理函数
                    const handler = instance.props[eventName]
                    if (handler) {
                        // 调用事件处理函数并传递参数
                        handler(...payload)
                    } else {
                        console.error('事件不存在')
                    }
                }

                // 将 emit 函数添加到 setupContext 中，用户可以通过 setupContext 取得 emit 函数
                // 将 slots 对象添加到 setupContext 中
                const setupContext = { attrs, emit, slots }

                // 在调用 setup 函数之前，设置当前组件实例
                setCurrentInstance(instance)

                // 调用 setup 函数，将只读版本的 props 作为第一个参数传递，避免用户意外地修改 props 的值，
                // 将 setupContext 作为第二个参数传递
                const setupResult = setup&&setup(shallowReadonly(instance.props), setupContext)

                // 在 setup 函数执行完毕之后，重置当前组件实例
                setCurrentInstance(null)

                // setupState 用来存储由 setup 返回的数据
                let setupState = null
                // 如果 setup 函数的返回值是函数，则将其作为渲染函数
                if (typeof setupResult === 'function') {
                    // 报告冲突
                    if (render) console.error('setup 函数返回渲染函数，render 选项将被忽略')
                    // 将 setupResult 作为渲染函数
                    render = setupResult
                } else {
                    // 如果 setup 的返回值不是函数，则作为数据状态赋值给 setupState
                    setupState = setupResult
                }

                // 将组件实例设置到 vnode 上，用于后续更新
                vnode.component = instance

                // 创建渲染上下文对象，本质上是组件实例的代理
                const renderContext = new Proxy(instance, {
                    get(t, k, r) {
                        // 取得组件自身状态与 props 数据
                        const { state, props, slots } = t 
                        if (k === '$slots') return slots;
                        // 先尝试读取自身状态数据
                        if (state && k in state) {
                            return state[k]
                        } else if (k in props) { // 如果组件自身没有该数据，则尝试从 props 中读取
                            return props[k]
                        } else if(setupState && k in setupState){
                            // 渲染上下文需要增加对 setupState 的支持
                            return setupState[k]
                        } else {
                            console.error('不存在')
                        }
                    },
                    set (t, k, v, r) {
                        const { state, props } = t
                        if (state && k in state) {
                            state[k] = v
                        } else if (k in props) {
                            console.warn(`Attempting to mutate prop "${k}". Props are readonly.`)
                        } else if (setupState && k in setupState){
                            // 渲染上下文需要增加对 setupState 的支持
                            setupState[k] = v
                        } else {
                            console.error('不存在')
                        }
                    }
                })
                // 生命周期函数调用时要绑定渲染上下文对象
                // 下面的 state 替换为 renderContext
                // 在这里调用 created 钩子
                created && created.call(renderContext)

                // 将组件的 render 函数调用包装到 effect 内
                effect(()=>{
                    // 调用 render 函数时，将其 this 设置为 state，
                    // 从而 render 函数内部可以通过 this 访问组件自身状态数据
                    // 执行渲染函数，获取组件要渲染的内容，即 render 函数返回的虚拟 DOM
                    const subTree = render.call(renderContext, renderContext)
                    
                    // 检查组件是否已经被挂载
                    if (!instance.isMounted) {
                        // 在这里调用 beforeMount 钩子
                        beforeMount && beforeMount.call(renderContext)
                        // 初次挂载，调用 patch 函数第一个参数传递 null
                        patch(null, subTree, container, anchor)
                        // 重点：将组件实例的 isMounted 设置为 true，这样当更新发生时就不会再次进行挂载操作，
                        // 而是会执行更新
                        instance.isMounted = true
                        // 在这里调用 mounted 钩子
                        mounted && mounted.call(renderContext)

                        // 遍历 instance.mounted 数组并逐个执行即可
                        instance.mounted && instance.mounted.forEach(hook => hook.call(renderContext))
                    } else {
                        // 在这里调用 beforeUpdate 钩子
                        beforeUpdate && beforeUpdate.call(renderContext)
                        // 当 isMounted 为 true 时，说明组件已经被挂载，只需要完成自更新即可，
                        // 所以在调用 patch 函数时，第一个参数为组件上一次渲染的子树，
                        // 意思是，使用新的子树与上一次渲染的子树进行打补丁操作
                        patch(instance.subTree, subTree, container, anchor)
                        // 在这里调用 updated 钩子
                        updated && updated.call(renderContext)
                    }
                    // 更新组件实例的子树
                    instance.subTree = subTree
                    
                }, {
                    // 指定该副作用函数的调度器为 queueJob 即可
                    scheduler: queueJob
                })

                // 任务缓存队列，用一个 Set 数据结构来表示，这样就可以自动对任务进行去重
                const queue = new Set()
                // 一个标志，代表是否正在刷新任务队列
                let isFlushing = false
                // 创建一个立即 resolve 的 Promise 实例
                const p = Promise.resolve()

                // 调度器的主要函数，用来将一个任务添加到缓冲队列中，并开始刷新队列
                function queueJob(job){
                    // 将 job 添加到任务队列 queue 中
                    queue.add(job)
                    // 如果还没有开始刷新队列，则刷新之
                    if (!isFlushing) {
                        // 将该标志设置为 true 以避免重复刷新
                        isFlushing = true
                        // 在微任务中刷新缓冲队列
                        p.then(() => {
                            try {
                                // 执行任务队列中的任务
                                queue.forEach(job => job())
                            } finally {
                                // 重置状态
                                isFlushing = false
                                queue.clear()
                            }
                        })
                    }
                }

                // resolveProps 函数用于解析组件 props 和 attrs 数据
                function resolveProps(options, propsData) {
                    const props = {}
                    const attrs = {}
                    // 遍历为组件传递的 props 数据
                    for (const key in propsData) {
                        // 以字符串 on 开头的 props，无论是否显式地声明，
                        // 都将其添加到 props 数据中，而不是添加到 attrs 中
                        if (key in options || key.startsWith('on')) {
                            // 如果为组件传递的 props 数据在组件自身的 props 选项中有定义，则将其视为合法的 props
                            props[key] = propsData[key]
                        } else {
                            // 否则将其作为 attrs
                            attrs[key] = propsData[key]
                        }
                    }
                    // 最后返回 props 与 attrs 数据
                    return [ props, attrs ]
                }
            }

            /**
             * 完成子组件的更新（组件被动更新的最小实现）
             * @param {VNode} n1 旧的 vnode
             * @param {VNode} n2 新的 vnode
             * @param {HTMLElement} container 容器
             * */ 
             function patchComponent(n1, n2, container) {
                // 获取组件实例，即 n1.component，同时让新的组件虚拟节点 n2.component 也指向组件实例
                const instance = (n2.component = n1.component)
                // 获取当前的 props 数据
                const { props } = instance
                console.log('props.title pre', props.title.value);
                // 调用 hasPropsChanged 检测为子组件传递的 props 是否发生变化，如果没有变化，则不需要更新
                if (hasPropsChanged(n1.props, n2.props)) {
                    // 调用 resolveProps 函数重新获取 props 数据
                    const [ nextProps ] = resolveProps(n2.type.props, n2.props)
                    // 更新 props
                    for (const k in nextProps) {
                        props[k] = nextProps[k]
                    }
                    // 删除不存在的 props
                    for (const k in props) {
                        if (!(k in nextProps)) delete props[k]
                    }
                }
                
                console.log('props.title next', props.title.value);

                /**
                 * 检测 props 是否发生变化
                 * @param {Object} prevProps 旧的 props
                 * @param {Object} nextProps 新的 props
                 * */
                function hasPropsChanged(prevProps, nextProps) {
                    const nextKeys = Object.keys(nextProps)
                    // 如果新旧 props 的数量变了，则说明有变化
                    if (nextKeys.length !== Object.keys(prevProps).length) {
                        return true
                    }
                    for (let i = 0; i < nextKeys.length; i++) {
                        const key = nextKeys[i]
                        // 有不相等的 props，则说明有变化
                        if (nextProps[key] !== prevProps[key]) return true
                    }
                    return false
                }
            }

            // 第一个参数 n1 代表旧 vnode，第二个参数 n2 代表新 vnode。
            /**
             * 第一个参数 n1 代表旧 vnode，第二个参数 n2 代表新 vnode。
             * @param {VNode} n1 旧的 vnode
             * @param {VNode} n2 新的 vnode
             * @param {HTMLElement} container 容器
             * @param {HTMLElement} anchor 锚点元素
             * */ 
            function patch(n1, n2, container, anchor) {
                 // 如果 n1 存在，则对比 n1 和 n2 的类型
                 if (n1 && n1.type !== n2.type) {
                    // 如果新旧 vnode 的类型不同，则直接将旧 vnode 卸载
                    unmount(n1)
                    n1 = null
                }

                // 代码运行到这里，证明 n1 和 n2 所描述的内容相同
                const { type } = n2
                // 如果 n2.type 的值是字符串类型，则它描述的是普通标签元素
                if (typeof type === 'string') {
                    // 如果 n1 不存在，意味着挂载，则调用 mountElement 函数完成挂载
                    if (!n1) {
                        mountElement(n2, container, anchor)
                    } else {
                        patchElement(n1, n2)
                    }
                } else if (type === Text){
                    // 如果新 vnode 的类型是 Text，则说明该 vnode 描述的是文本节点

                    // 如果没有旧节点，则进行挂载
                    if (!n1) {
                        // 调用 createText 函数创建文本节点
                        const el = n2.el = createText(n2.children);
                        // 将文本节点插入到容器中
                        insert(el, container)
                    } else {
                        // 如果旧 vnode 存在，只需要使用新文本节点的文本内容更新旧文本节点即可
                        const el = n2.el = n1.el
                        if (n2.children !== n1.children) {
                            // 调用 setText 函数更新文本节点的内容
                            setText(el, n2.children)
                        }
                    }

                } else if (type === Comment){
                    if (!n1) {
                        const el = n2.el = createComment(n2.children);
                        insert(el, container)
                    } else {
                        const el = n2.el = n1.el
                        if (n2.children !== n1.children) {
                            setText(el, n2.children)
                        }
                    }


                } else if (type === Fragment){
                     // 处理 Fragment 类型的 vnode
                    if (!n1) {
                        // 如果旧 vnode 不存在，则只需要将 Fragment 的 children 逐个挂载即可
                        n2.children.forEach(c => patch(null, c, container))
                    } else {
                        // 如果旧 vnode 存在，则只需要更新 Fragment 的 children 即可
                        patchChildren(n1, n2, container)
                    }

                } else if (typeof type === 'object' && type.__isTeleport){
                    // 组件选项中如果存在 __isTeleport 标识，则它是 Teleport 组件，
                    // 调用 Teleport 组件选项中的 process 函数将控制权交接出去
                    // 传递给 process 函数的第五个参数是渲染器的一些内部方法
                    type.process(n1, n2, container, anchor, {
                        patch,
                        patchChildren,
                        unmount,
                        move(vnode, container, anchor){
                            console.log('更新 Teleport move');
                            insert(
                                vnode.component ? 
                                vnode.component.subTree.el :  // 移动一个组件
                                vnode.el, // 移动普通元素
                                container, anchor
                            )
                        }
                    })
                } else if (
                    // type 是对象 --> 有状态组件
                    // type 是函数 --> 函数式组件
                    typeof type === 'object' || 
                    typeof type === 'function'
                ) {
                    // 如果 n2.type 的值的类型是对象，则它描述的是组件
                    if (!n1) {
                        // 如果该组件已经被 KeepAlive，则不会重新挂载它，而是会调用 _activate 来激活它
                        if (n2.keptAlive) {
                            // 激活组件
                            n2.keepAliveInstance._activate(n2, container, anchor)
                        } else {
                            // 挂载组件
                            mountComponent(n2, container, anchor)
                        }
                    } else {
                        // 更新组件
                        patchComponent(n1, n2, container)
                    }
                } else if (type === 'xxx') {
                    // 处理其他类型的 vnode
                }
            }

            function render(vnode, container) {
                if (vnode) {
                    // 新 vnode 存在，将其与旧 vnode 一起传递给 patch 函数，进行打补丁
                    patch(container._vnode, vnode, container)
                } else {
                    if (container._vnode) {
                        // 旧 vnode 存在，且新 vnode 不存在，说明是卸载（unmount）操作
                        // 调用 unmount 函数卸载 vnode
                        unmount(container._vnode)
                    }
                }
                // 把 vnode 存储到 container._vnode 下，即后续渲染中的旧 vnode
                container._vnode = vnode
            }

            // 卸载操作
            function unmount(vnode) {
                // 判断 VNode 是否需要过渡处理
                const needTransition = vnode.transition // 新增

                // 在卸载时，如果卸载的 vnode 类型为 Fragment，则需要卸载其 children
                if (vnode.type === Fragment) {
                    vnode.children.forEach(c => unmount(c))
                    return
                }

                // 如果 vnode.type 是对象，则说明该 vnode 描述的是组件
                if(typeof vnode.type === 'object'){
                    // vnode.shouldKeepAlive 是一个布尔值，用来标识该组件是否应该被 KeepAlive
                    if (vnode.shouldKeepAlive){
                        // 对于需要被 KeepAlive 的组件，我们不应该真的卸载它，而应调用该组件的父组件，
                        // 即 KeepAlive 组件的 _deActivate 函数使其失活
                        vnode.keepAliveInstance._deActivate(vnode)
                    }else{
                        // 调用组件实例的 unmounted 钩子函数
                        vnode.component.unmounted.forEach(hook => hook())
                        // 卸载组件
                        unmount(vnode.component.subTree)
                    }
                    
                    return
                }

                const parent = vnode.el.parentNode
                if (parent) {
                    // 将卸载动作封装到 performRemove 函数中
                    const performRemove = () => parent.removeChild(vnode.el) // 新增

                    if (needTransition) { // 新增
                        // 如果需要过渡处理，则调用 transition.leave 钩子，
                        // 同时将 DOM 元素和 performRemove 函数作为参数传递
                        vnode.transition.leave(vnode.el, performRemove)
                    } else {
                        // 如果 vnode 不需要过渡处理，则直接调用 performRemove 函数
                        performRemove()
                    }
                }
            }

            return {
                render
            }
        }

        // 创建一个渲染器
        // 在创建 renderer 时传入配置项
        const renderer = createRenderer({
            // 用于创建元素
            createElement(tag) {
                return document.createElement(tag)
            },
            // 用于设置元素的文本节点
            setElementText(el, text) {
                el.textContent = text
            },
            /**
             * 用于在给定的 parent 下添加指定元素
             * @param {HTMLElement} el  用于插入的节点
             * @param {HTMLElement} parent  新插入节点的父节点
             * @param {HTMLElement} anchor  将要插在这个节点之前
             * */ 
            insert(el, parent, anchor = null) {
                parent.insertBefore(el, anchor)
            },
            // 将属性设置相关操作封装到 patchProps 函数中，并作为渲染器选项传递
            patchProps(el, key, prevValue, nextValue) {
                if (/^on/.test(key)) {
                    // 匹配以 on 开头的属性，视其为事件

                    // 定义 el._vei 为一个对象，存在事件名称到事件处理函数的映射
                    const invokers = el._vei || (el._vei = {})
                    //根据事件名称获取 invoker
                    let invoker = invokers[key]
                    // 根据属性名称得到对应的事件名称，例如 onClick ---> click
                    const name = key.slice(2).toLowerCase()
                    if(nextValue){
                        if (!invoker) {
                            // 如果没有 invoker，则将一个伪造的 invoker 处理函数缓存到 el._vei[key] 下，避免覆盖
                            // vei 是 vue event invoker 的首字母缩写
                            invoker = el._vei[key] = (e) => {
                                // e.timeStamp 是事件发生的时间
                                // 如果事件发生的时间早于事件处理函数绑定的时间，则不执行事件处理函数
                                if (e.timeStamp < invoker.attached) return;
                                // 如果 invoker.value 是数组，则遍历它并逐个调用事件处理函数
                                if (Array.isArray(invoker.value)) {
                                    invoker.value.forEach(fn => fn(e))
                                } else {
                                    // 否则直接作为函数调用
                                    // 当伪造的事件处理函数执行时，会执行真正的事件处理函数
                                    invoker.value(e)
                                }
                            }
                            // 将真正的事件处理函数赋值给 invoker.value
                            invoker.value = nextValue
                            // 添加 invoker.attached 属性，存储事件处理函数被绑定的时间
                            invoker.attached = performance.now();
                            // 绑定 invoker 作为事件处理函数
                            el.addEventListener(name, invoker)
                        } else {
                            // 如果 invoker 存在，意味着更新，并且只需要更新 invoker.value 的值即可
                            invoker.value = nextValue
                        }
                    } else if (invoker) {
                        // 新的事件绑定函数不存在，且之前绑定的 invoker 存在，则移除绑定
                        el.removeEventListener(name, invoker)
                    }
                } else if (key === 'class') {
                    // 对 class 进行特殊处理
                    el.className = nextValue || ''
                } else if (shouldSetAsProps(el, key, nextValue)) {
                    // 获取该 DOM Properties 的类型
                    const type = typeof el[key]
                    // 如果是布尔类型，并且 value 是空字符串，则将值矫正为 true
                    if (type === 'boolean' && nextValue === '') {
                        el[key] = true
                    } else {
                        el[key] = nextValue
                    }
                } else {
                    el.setAttribute(key, nextValue)
                }

                // 判断是否只读，如果是，则使用 setAttribute 函数设置属性
                function shouldSetAsProps(el, key, value) {
                    // 特殊处理
                    if (key === 'form' && el.tagName === 'INPUT') return false
                    // 兜底
                    // 用 in 操作符判断 key 是否存在对应的 DOM Properties
                    return key in el
                }
            },
            createText(text) {
                return document.createTextNode(text)
            },
            setText(el, text) {
                el.nodeValue = text
            },
            createComment(text) {
                return document.createComment(text)
            },
        })

        /**
         * defineAsyncComponent 函数用于定义一个异步组件，接收一个异步组件加载器作为参数
         * @param {Object} options 配置对象
         * @param {Function} options.loader 异步组件加载器
         * @param {number} options.timeout 超时时长，其单位为 ms
         * @param {number} options.delay 延迟时长，其单位为 ms
         * @param {Object} options.errorComponent 指定出错时要渲染的组件
         * @param {Object} options.loadingComponent 指定加载过程中要渲染的组件
         * @param {Function} options.onError 出错时的回调函数 onError(retry<function>, fail<function>, retries<number>)
         * @returns {Object} 返回一个包装组件
         * */ 
        function defineAsyncComponent(options) {
        // options 可以是配置项，也可以是加载器
        if (typeof options === 'function') {
            // 如果 options 是加载器，则将其格式化为配置项形式
            options = {
                loader: options
            }
        }
        const { loader } = options

        // 一个变量，用来存储异步加载的组件
        let InnerComp = null

        // 记录重试次数
        let retries = 0 
        // 封装 load 函数用来加载异步组件
        function load() {
            // 执行加载器函数，返回一个 Promise 实例
            return loader()
            // 捕获加载器的错误
            .catch((err) => {
                // 如果用户指定了 onError 回调，则将控制权交给用户
                if (options.onError) {
                    // 返回一个新的 Promise 实例
                    return new Promise((resolve, reject) => {
                        // 重试
                        const retry = () => {
                            resolve(load())
                            retries++
                        }
                        // 失败
                        const fail = () => reject(err)
                        // 作为 onError 回调函数的参数，让用户来决定下一步怎么做
                        options.onError(retry, fail, retries)
                    })
                } else {
                    throw err
                }
            })
        }

        // 返回一个包装组件
        return {
            name: 'AsyncComponentWrapper',
            setup() {
                // 异步组件是否加载成功
                const loaded = ref(false)
                // 定义 error，当错误发生时，用来存储错误对象
                const error = ref(null)
                
                // 一个标志，代表是否正在加载，默认为 false
                const loading = ref(false)
                let loadingTimer = null
                // 如果配置项中存在 delay，则开启一个定时器计时，当延迟到时后将 loading.value 设置为 true
                if (options.delay) {
                    loadingTimer = setTimeout(() => {
                        loading.value = true
                    }, options.delay);
                } else {
                    // 如果配置项中没有 delay，则直接标记为加载中
                    loading.value = true
                }

                // 执行加载器函数，返回一个 Promise 实例
                // 加载成功后，将加载成功的组件赋值给 InnerComp，并将 loaded 标记为 true，代表加载成功
                // loader()
                load() // 替换 loader
                .then(c => {
                    // 浏览器模拟需要访问 default 属性
                    InnerComp = c.default
                    loaded.value = true
                })
                // 添加 catch 语句来捕获加载过程中的错误
                .catch((err) => {
                    error.value = err;
                    clearTimeout(timer) //  自己加的 感觉效果好点
                }) 
                // 加载完毕后，无论成功与否都要清除延迟定时器
                .finally(() => {
                    loading.value = false
                    clearTimeout(loadingTimer)
                })

                let timer = null // 超时错误计时器
                if (options.timeout) {
                    // 如果指定了超时时长，则开启一个定时器计时
                    timer = setTimeout(() => {
                        // 超时后创建一个错误对象，并复制给 error.value
                        const err = new Error(`Async component timed out after ${options.timeout}ms.`)
                        error.value = err
                    }, options.timeout)
                }
                // 包装组件被卸载时清除定时器
                onUnmounted&&onUnmounted(() => {
                    clearTimeout(timer)
                })

                // 占位内容
                const placeholder = { type: Text, children: 'placeholder' }

                return () => {
                    if(loaded.value){
                        // 如果组件异步加载成功，则渲染被加载的组件
                        return { type: InnerComp }
                    }else if(error.value && options.errorComponent){
                        // 只有当错误存在且用户配置了 errorComponent 时才展示 Error 组件，同时将 error 作为 props 传递
                        return { type: options.errorComponent, props: { error: error.value } }
                    }else if(loading.value && options.loadingComponent){
                        // 如果异步组件正在加载，并且用户指定了 Loading 组件，则渲染 Loading 组件
                        return { type: options.loadingComponent }
                    }else{
                        return placeholder
                    }
                }
            }
        }
    }
    
        // KeepAlive 组件的实现
        const KeepAlive = {
            // KeepAlive 组件独有的属性，用作标识
            __isKeepAlive: true,
            // 定义 include 和 exclude
            props: {
                include: RegExp,
                exclude: RegExp
            },
            setup(props, { slots }) {
                // 创建一个缓存对象
                // key: vnode.type
                // value: vnode
                const cache = new Map()
                // 当前 KeepAlive 组件的实例
                const instance = currentInstance
                // 对于 KeepAlive 组件来说，它的实例上存在特殊的 keepAliveCtx 对象，该对象由渲染器注入
                // 该对象会暴露渲染器的一些内部方法，其中 move 函数用来将一段 DOM 移动到另一个容器中
                const { move, createElement } = instance.keepAliveCtx

                // 创建隐藏容器
                const storageContainer = createElement('div')

                // KeepAlive 组件的实例上会被添加两个内部函数，分别是 _deActivate 和 _activate
                // 这两个函数会在渲染器中被调用
                instance._deActivate = (vnode) => {
                    console.log('失活', vnode);
                    move(vnode, storageContainer)
                }
                instance._activate = (vnode, container, anchor) => {
                    console.log('激活', vnode);
                    move(vnode, container, anchor)
                }

                return () => {
                    // KeepAlive 的默认插槽就是要被 KeepAlive 的组件
                    let rawVNode = slots.default()
                    // 如果不是组件，直接渲染即可，因为非组件的虚拟节点无法被 KeepAlive
                    if (typeof rawVNode.type !== 'object') {
                        return rawVNode
                    }
                    
                    // 获取“内部组件”的 name
                    const name = rawVNode.type.name
                    // 对 name 进行匹配
                    if(
                        name &&
                        (
                            // 如果 name 无法被 include 匹配
                            (props.include && !props.include.test(name)) ||
                            // 或者被 exclude 匹配
                            (props.exclude && props.exclude.test(name))
                        )
                    ) {
                        console.log('不缓存', rawVNode);
                        return rawVNode
                    }

                    // 在挂载时先获取缓存的组件 vnode
                    const cachedVNode = cache.get(rawVNode.type)
                    if (cachedVNode) {
                        // 如果有缓存的内容，则说明不应该执行挂载，而应该执行激活
                        // 继承组件实例
                        rawVNode.component = cachedVNode.component
                        // 在 vnode 上添加 keptAlive 属性，标记为 true，避免渲染器重新挂载它
                        rawVNode.keptAlive = true
                    } else {
                        // 如果没有缓存，则将其添加到缓存中，这样下次激活组件时就不会执行新的挂载动作了
                        cache.set(rawVNode.type, rawVNode)
                    }

                    // 在组件 vnode 上添加 shouldKeepAlive 属性，并标记为 true，避免渲染器真的将组件卸载
                    rawVNode.shouldKeepAlive = true
                    // 将 KeepAlive 组件的实例也添加到 vnode 上，以便在渲染器中访问
                    rawVNode.keepAliveInstance = instance

                    // 渲染组件 vnode
                    return rawVNode
                }
            }
        }

        const Teleport = {
            __isTeleport: true,
            process(n1, n2, container, anchor, internals) {
                // 通过 internals 参数取得渲染器的内部方法
                const { patch, patchChildren, move } = internals
                // 如果旧 VNode n1 不存在，则是全新的挂载，否则执行更新
                if (!n1) {
                    // 挂载
                    // 获取容器，即挂载点
                    const target = typeof n2.props.to === 'string'
                        ? document.querySelector(n2.props.to)
                        : n2.props.to
                    // 将 n2.children 渲染到指定挂载点即可
                    n2.children.forEach(c => patch(null, c, target, anchor))
                } else {
                    // 更新 
                    // 更新子节点
                    patchChildren(n1, n2, container)
                    
                    // 如果新旧 to 参数的值不同，则需要对内容进行移动
                    if (n2.props.to !== n1.props.to) {
                        // 获取新的容器
                        const newTarget = typeof n2.props.to === 'string'
                            ? document.querySelector(n2.props.to)
                            : n2.props.to
                        // 移动到新的容器
                        n2.children.forEach(c => move(c, newTarget))
                    }
                }
            }
        }

        // 新增 Transition 组件
        const Transition = {
            name: 'Transition',
            setup(props, { slots }) {
                return () => {
                    // 通过默认插槽获取需要过渡的元素
                    const innerVNode = slots.default()

                    // 在过渡元素的 VNode 对象上添加 transition 相应的钩子函数
                    innerVNode.transition = {
                        beforeEnter(el) {
                            // 设置初始状态：添加 enter-from 和 enter-active 类
                            el.classList.add('enter-from')
                            el.classList.add('enter-active')
                        },
                        enter(el) {
                            // 在下一帧切换到结束状态
                            nextFrame(() => {
                                // 移除 enter-from 类，添加 enter-to 类
                                el.classList.remove('enter-from')
                                el.classList.add('enter-to')
                                // 监听 transitionend 事件完成收尾工作
                                el.addEventListener('transitionend', () => {
                                    el.classList.remove('enter-to')
                                    el.classList.remove('enter-active')
                                })
                            })
                        },
                        leave(el, performRemove) {
                            // 设置离场过渡的初始状态：添加 leave-from 和 leave-active 类
                            el.classList.add('leave-from')
                            el.classList.add('leave-active')
                            // 强制 reflow，使得初始状态生效
                            document.body.offsetHeight
                            // 在下一帧修改状态
                            nextFrame(() => {
                                // 移除 leave-from 类，添加 leave-to 类
                                el.classList.remove('leave-from')
                                el.classList.add('leave-to')

                                // 监听 transitionend 事件完成收尾工作
                                el.addEventListener('transitionend', () => {
                                    el.classList.remove('leave-to')
                                    el.classList.remove('leave-active')
                                    // 调用 transition.leave 钩子函数的第二个参数，完成 DOM 元素的卸载
                                    performRemove()
                                })
                            })
                        }
                    }

                    // 渲染需要过渡的元素
                    return innerVNode
                }

                function nextFrame(cb) {
                    requestAnimationFrame(() => {
                        requestAnimationFrame(cb)
                    })
                }
            }
        }
        

        // 新增 测试
        const trigger = ref(false)
        const CompVNode = {
            type: Transition,
            children: {
                default() {
                    return  { type: 'p', children: '我是需要过渡的元素 testA...' }
                }
            }
        }

        
        setTimeout(() => {
            trigger.value = !trigger.value;
        }, 2000)

        
        effect(()=>{
            // 调用渲染器来渲染组件
            renderer.render(trigger.value?null:CompVNode, document.querySelector('#app'));
        })

    </script>
    <!-- 新增 样式 -->
    <style>
        .enter-from{
            transform: translateX(200px);
        }
        .enter-to{
            transform: translateX(0);
        }
        .enter-active{
            transition: transform 1s ease-in-out;
        }
        /* 初始状态 */
        .leave-from {
            transform: translateX(0);
        }
        /* 结束状态 */
        .leave-to {
            transform: translateX(200px);
        }
        /* 过渡过程 */
        .leave-active {
            transition: transform 2s ease-out;
        }
    </style>

</html>